#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <sqlite3.h>
#import <UIKit/UIApplication.h>
#import <SpringBoard/SBApplicationController.h>
#import <SpringBoard/SBApplication.h>
#import "../LockInfo/Plugin.h"

#import "PIPlugin.h"
#import "PIIntegration.h"
#import "PITodoView.h"
#import "PIDotView.h"
#import "PIActionView.h"
#import "PITodoPreview.h"


enum ShowDatesOptions {
	ShowDatesNever = 0,
	ShowDatesIfNeeded = 1,
	ShowDatesAlways = 2
};

enum ShortDatesOptions {
	ShortDatesNever = 0,
	ShortDatesIfNeeded = 1,
	ShortDatesAlways = 2
};

enum TaskFilingOptions {
	TaskFilingAnyTasks = 0,
	TaskFilingInboxOnly = 1,
	TaskFilingFiledOnly = 2
};

enum SortFieldOptions {
	SortFieldOptionsNone = 0,
	SortFieldOptionsImportance = 1,
	SortFieldOptionsPriority = 2,
	SortFieldOptionsAction = 3,
	SortFieldOptionsDueDate = 4,
	SortFieldOptionsStartDate = 5,
	SortFieldOptionsTitle = 6,
	SortFieldOptionsReorder = 7
};

enum SortFieldNoneOrder {
	SortFieldNoneOrderFirst = 0,
	SortFieldNoneOrderLast = 1
};


@implementation PIPlugin

@synthesize todoList, todoPrefs, lastSql, lastPrefs, plugin, prefsPath, dbPath, appPath, topColor, highColor, normalColor, lowColor, overdueColor;


-(void)dealloc {
    [todoList release];
    [todoPrefs release];
	[lastSql release];
	[lastPrefs release];
	[plugin release];
	[prefsPath release];
	[dbPath release];
	[appPath release];
	[topColor release];
	[highColor release];
	[normalColor release];
	[lowColor release];
	[overdueColor release];
	
    [super dealloc];
}


-(NSString*) iconPathFromAction:(int)action {
	NSString* actionIcon = [PIIntegration iconFileForAction:action];
	
	if (actionIcon != nil)
		actionIcon = [NSString stringWithFormat:@"%@/%@.png", self.appPath, actionIcon];
	
	return actionIcon;
}

-(UIColor*) colorFromAction:(int)action {
	if (action <= PIActionNone)
		return [UIColor clearColor];
	else if (action <= PIActionPlanning)
		return self.highColor;
	else if (action <= PIActionSomeday)
		return self.normalColor;
	else
		return self.lowColor;
}

-(UIColor*) colorFromImportance:(int)importance {
	if (importance <= PIImportanceNone)
		return [UIColor grayColor];
	else if (importance <= PIImportanceTop)
		return self.topColor;
	else if (importance <= PIImportanceHigh)
		return self.highColor;
	else if (importance <= PIImportanceNormal)
		return self.normalColor;
	else
		return self.lowColor;
}

-(NSString*) addSortField:(NSString*)current field:(int)field ascending:(BOOL)ascending invert:(BOOL)invert {
	NSString* fieldName = nil;
	
	switch (field) {
		case SortFieldOptionsImportance:
			if (invert)
				fieldName = @"case t.priority when 0 then 99 else t.priority end";
			else
				fieldName = @"t.priority";
			fieldName = [fieldName stringByAppendingFormat:@"/*Importance[%i]*/", invert];
			break;
		case SortFieldOptionsPriority:
			// Sort by initial letter if present or '-/~' if starts with a number - so numbers precede or follow letters
			// depending upon the invert flag state. If no value at all then use '~~' to ensure no fc value is always
			// at the end of the list.
			fieldName = [NSString stringWithFormat:@"case when ifnull(t.fcpriority,'')='' then '~~' when abs(substr(t.fcpriority,1,1))>0 then '%@' else substr(t.fcpriority,1,1) end", invert ? @"~" : @"-"];
			if (!ascending)
				fieldName = [fieldName stringByAppendingString:@" DESC"];
			fieldName = [fieldName stringByAppendingString:@", case when abs(substr(t.fcpriority,1,1))=0 then abs(substr(t.fcpriority,2)) else abs(t.fcpriority) end"];
			fieldName = [fieldName stringByAppendingFormat:@"/*Priority[%i]*/", invert];
			break;
		case SortFieldOptionsAction:
			if (invert)
				fieldName = @"case t.action when 0 then 99 else t.action end";
			else
				fieldName = @"t.action";
			fieldName = [fieldName stringByAppendingFormat:@"/*Action[%i]*/", invert];
			break;
		case SortFieldOptionsDueDate:
			// PI stores end times as local rather than UTC so we need to convert the local time to UTC
			// in order to compare with an end date. This will be a hack as srictly we need the number of
			// seconds offset on the given day to account for DST. That's the way that the display functions
			// work.
			if (invert)
				fieldName = [NSString stringWithFormat:@"coalesce(t.duedate,t.duedatetime+ %i,9999999999)",
							 [[NSTimeZone systemTimeZone] secondsFromGMTForDate:[NSDate date]]];
			else
				fieldName = [NSString stringWithFormat:@"ifnull(t.duedate,t.duedatetime+ %i)",
							 [[NSTimeZone systemTimeZone] secondsFromGMTForDate:[NSDate date]]];
			fieldName = [fieldName stringByAppendingFormat:@"/*Due[%i]*/", invert];
			break;
		case SortFieldOptionsStartDate:
			if (invert)
				fieldName = @"ifnull(t.startdate,9999999999)";
			else
				fieldName = @"t.startdate";
			fieldName = [fieldName stringByAppendingFormat:@"/*Start[%i]*/", invert];
			break;
		case SortFieldOptionsTitle:
			fieldName = @"t.title";
			fieldName = [fieldName stringByAppendingFormat:@"/*Title[%i]*/", invert];
			break;
		case SortFieldOptionsReorder:
			fieldName = @"t.sortfield";
			fieldName = [fieldName stringByAppendingFormat:@"/*Reorder[%i]*/", invert];
			break;
		default:
			break;
	}
	
	if (fieldName != nil) {
		if ([current length] > 0)
			current = [current stringByAppendingString:@", "];
		current = [current stringByAppendingString:fieldName];
		
		if (!ascending)
			current = [current stringByAppendingString:@" DESC"];
	}
	
	return current;
}

-(BOOL) getBoolValueFromPrefs:(NSDictionary*)prefs forName:(NSString*)name withDefault:(BOOL)def {
	if (NSNumber* n = [prefs valueForKey:name])
		return n.boolValue;
	return def;
}

-(int) getIntValueFromPrefs:(NSDictionary*)prefs forName:(NSString*)name withDefault:(int)def {
	if (NSNumber* n = [prefs valueForKey:name])
		return n.intValue;
	return def;
}

-(NSString*) buildSQLString:(NSDictionary*)prefs {
	DebugLog(@"LI:PITodo: Building SQL for prefs %@", prefs);
	
	// Get the standard information we'll need
	NSString *allSql = @"select t.title, t.startdate, t.duedate, t.duedatetime, t.priority, t.fcpriority, t.id, t.flagged, t.action, t.icon, p.title from task t left join context c on t.contextid = c.id left join task p on t.projectid = p.id left join itemtagmap m on m.itemid = t.id left join tag g on m.tagid = g.id where t.tasktype <> 1 and t.completeddate is null and (t.calendarid=-2 or t.calendarid>=0)";
	
	// Standard expessions
	static NSString* inbox = @"t.contextid<=0 and t.projectid<=0 and t.action<=0 and g.title is null";
	
	int taskFiling = [self getIntValueFromPrefs:prefs forName:@"showFiling" withDefault:TaskFilingAnyTasks];
	if (taskFiling == TaskFilingInboxOnly)
		// Only tasks without tags or a context or a project or an action
		allSql = [allSql stringByAppendingFormat:@" and %@/*HideInbox*/", inbox];
	else if (taskFiling == TaskFilingFiledOnly)
		// Don't allow tasks without tags or a context or a project or an action
		allSql = [allSql stringByAppendingFormat:@" and not(%@)/*HideFiled*/", inbox];
	
	if ([self getBoolValueFromPrefs:prefs forName:@"isHideDeferred" withDefault:false])
		// Only allow tasks before Delegated
		allSql = [allSql stringByAppendingFormat:@" and t.action<%d/*HideDelegated*/", PIActionDelegated];
	
	// Get the date maxDays in the future
	NSTimeInterval maxDays = (NSTimeInterval)[self getIntValueFromPrefs:prefs forName:@"dayLimit" withDefault:7];
	NSTimeInterval seconds = maxDays * SECONDS_PER_DAY;
	
	// Get time of midnight today (ie past) in the UTC by building a new date from only the year, month
	// and day components of the current date. PI start dates are always UTC
	NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
	gregorian.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
	NSDateComponents *components = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
												fromDate:[NSDate date]];
	NSDate *midnight = [gregorian dateFromComponents:components];
	DebugLog(@"LI:PITodo: Midnight date = %@", midnight);
	
	if ([self getBoolValueFromPrefs:prefs forName:@"isHideFuture" withDefault:true])
		// Only allow tasks starting before or at today's midnight or that don't have a start date
		allSql = [allSql stringByAppendingFormat:@" and ifnull(t.startdate,0)<=%F/*Hide Future*/", [midnight timeIntervalSinceReferenceDate]];
	
	// Get future midnight date by adding the number of days we will show (as seconds). Again this will be in UTC
	NSDate* future = [midnight addTimeInterval:seconds];
	DebugLog(@"LI:PITodo: Cutoff date = %@", future);
	
	// PI stores end times as local rather than UTC so we need to convert the local time to UTC
	// in order to compare with an end date. This will be a hack as srictly we need the number of
	// seconds offset on the given day to account for DST. That's the way that the display functions
	// work.
	NSInteger utcOffset = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:midnight];
	
	// Only allow items with no duedate or a duedate before the threshold (not at as that would give 1 day too much)
	allSql = [allSql stringByAppendingFormat:@" and coalesce(t.duedate,t.duedatetime+ %i,0)<%F/*MaxLimit*/", utcOffset, [future timeIntervalSinceReferenceDate]];
	
	if ([self getBoolValueFromPrefs:prefs forName:@"isLockscreenOnly" withDefault:false])
		// Only allow items with a tag named 'Lockscreen'
		allSql = [allSql stringByAppendingString:@" and g.title like 'Lockscreen'/*TagLockscreen*/"];
	
	if (NSString* tagFilter = [prefs valueForKey:@"tagFilter"]) {
		if ([tagFilter length] > 0)
		{
			// Only allow items with a tag that matches the requested value
			tagFilter = [tagFilter stringByReplacingOccurrencesOfString:@"'" withString:@"''"];
			allSql = [allSql stringByAppendingFormat:@" and g.title like '%@'/*TagAny*/", tagFilter];
		}
	}
	
	if ([self getBoolValueFromPrefs:prefs forName:@"isHideUndated" withDefault:false])
		// Only allow items that don't have a duedate or duedatetime
		allSql = [allSql stringByAppendingString:@" and ifnull(t.duedate,t.duedatetime) is not null/*HideUndated*/"];
	
	int actionType = [self getIntValueFromPrefs:prefs forName:@"showOnlyAction" withDefault:-1];
	if (actionType >= 0)
		// Only allow items with the correct action
		allSql = [allSql stringByAppendingFormat:@" and t.action=%i/*OnlyAction*/", actionType];
	
	if ([self getBoolValueFromPrefs:prefs forName:@"isStarredOnly" withDefault:false])
		// Only allow items that have been flagged
		allSql = [allSql stringByAppendingString:@" and t.flagged=1/*StarredOnly*/"];
	
	allSql = [allSql stringByAppendingString:@" group by t.id"];
	
	// Add sorting terms
	NSString* sortExpr = @"";
	
	int sort1 = [self getIntValueFromPrefs:prefs forName:@"sortField1" withDefault:SortFieldOptionsImportance];
	BOOL sortAsc1 = [self getBoolValueFromPrefs:prefs forName:@"isSortAsc1" withDefault:true];
	int noneLast1 = [self getIntValueFromPrefs:prefs forName:@"sortNoneOrder1" withDefault:SortFieldNoneOrderFirst];
	sortExpr = [self addSortField:sortExpr field:sort1 ascending:sortAsc1 invert:(noneLast1 == SortFieldNoneOrderLast)];
	
	int sort2 = [self getIntValueFromPrefs:prefs forName:@"sortField2" withDefault:SortFieldOptionsDueDate];
	BOOL sortAsc2 = [self getBoolValueFromPrefs:prefs forName:@"isSortAsc2" withDefault:true];
	int noneLast2 = [self getIntValueFromPrefs:prefs forName:@"sortNoneOrder2" withDefault:SortFieldNoneOrderFirst];
	if (sort1 != sort2)
		sortExpr = [self addSortField:sortExpr field:sort2 ascending:sortAsc2 invert:(noneLast2 == SortFieldNoneOrderLast)];
	
	if ([sortExpr length] > 0)
		allSql = [allSql stringByAppendingFormat:@" order by %@", sortExpr];
	
	// Restrict the number of items returned to the requested number		
	int queryLimit = [self getIntValueFromPrefs:prefs forName:@"maxTasks" withDefault:5];
	allSql = [allSql stringByAppendingFormat:@" limit %i", queryLimit];
	
	return allSql;
}

// Get PI colors or use the same defaults as PI
-(void)setApplicationColors:(NSDictionary*)prefs {
	NSString* key = @"TasksViewNavigationControllerDictIndexPath";
	NSIndexPath* path = nil;
	NSDictionary* dictKeys=[prefs objectForKey:key];
	if ([dictKeys count]) {
		@try {
			if ([dictKeys objectForKey: key]) {
				path = (NSIndexPath *)[NSKeyedUnarchiver unarchiveObjectWithData:[dictKeys objectForKey: key]];
				DebugLog(@"LI:PITodo: Path %@", path);
			}
		}
		@catch (NSException * e) {
			// Didn't work!
		}
	}
	
	// Top importance color
	self.topColor = [PIIntegration topImportanceBGColorForPrefs:prefs];
	self.highColor = [PIIntegration highImportanceBGColorForPrefs:prefs];
	self.normalColor = [PIIntegration normalImportanceBGColorForPrefs:prefs];
	self.lowColor = [PIIntegration lowImportanceBGColorForPrefs:prefs];
	self.overdueColor =[PIIntegration overdueBGColorForPrefs:prefs];	
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return self.todoList.count;
}


#pragma mark LITableViewDataSource methods

- (NSInteger)tableView:(LITableView*)tableView numberOfItemsInSection:(NSInteger)section {
	return self.todoList.count;
}

- (NSInteger)tableView:(LITableView*)tableView totalNumberOfItemsInSection:(NSInteger)section {
	return self.todoList.count;
}

-(void) tableView:(LITableView*) table reloadDataInSection:(NSInteger) section {
	[self updateTasks];
}

- (UITableViewCell *)tableView:(LITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	NSBundle* bundle = [NSBundle bundleForClass:[self class]];
	
	int showDate = [self getIntValueFromPrefs:self.plugin.preferences forName:@"showDates" withDefault:ShowDatesAlways];
	int shortDates = [self getIntValueFromPrefs:self.plugin.preferences forName:@"shortDates" withDefault:ShortDatesIfNeeded];
	BOOL showIcons = [self getBoolValueFromPrefs:self.plugin.preferences forName:@"isShowIcons" withDefault:true];
	BOOL showOverdue = [self getBoolValueFromPrefs:self.plugin.preferences forName:@"isHighlightOverdue" withDefault:true];
	
	NSDictionary* elem = [self.todoList objectAtIndex:indexPath.row];
	
	NSString* name = [elem objectForKey:@"name"];
	NSString* icon = [elem objectForKey:@"icon"];
	if ([icon length] > 0)
		name = [NSString stringWithFormat:@"%@ %@", name, icon];
	
	int importance = [[elem objectForKey:@"importance"] intValue];
	NSString* priority = [elem objectForKey:@"priority"];
	NSString* project = [elem objectForKey:@"project"];
	int flagged = [[elem objectForKey:@"flagged"] intValue];
	int action = [[elem objectForKey:@"action"] intValue];
	
	NSNumber* startNum = [elem objectForKey:@"start"];
	NSNumber* dateNum = [elem objectForKey:@"due"];
	NSNumber* timeNum = [elem objectForKey:@"duetime"];
	
	// If there is a due date/time then use that instead of the due date
	if (timeNum.doubleValue != 0)
		dateNum = timeNum;
	
	NSString* due = nil;
	NSString* shortDue = nil;
	BOOL overdue = false;
	
	BOOL dateLine = (showDate == ShowDatesAlways) ||
	((showDate == ShowDatesIfNeeded) && ((startNum.doubleValue != 0) || (dateNum.doubleValue != 0) || ([priority length] > 0)));
	
	static NSString *MyIdentifier = @"PITodoCell";
	static int MyTag = 57;
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
		cell.backgroundColor = [UIColor clearColor];
		cell.autoresizingMask = UIViewAutoresizingFlexibleWidth;
		
		PITodoView* v = [PITodoView createView:CGRectMake(0, 0, tableView.frame.size.width, 35) table:tableView];
		v.tag = MyTag;
		[cell.contentView addSubview:v];
	}
	PITodoView* v = [cell.contentView viewWithTag:MyTag];
	
	if (dateLine) {		
		if ((dateNum.doubleValue == 0.0) && (startNum.doubleValue == 0.0)) {
			due = localize(bundle, @"Date/No Due Date");
		}
		else {
			BOOL timeElement = (timeNum.doubleValue != 0);
			NSDate* startDate = [PIIntegration localDateForDate:startNum.doubleValue];
			NSDate* dueDate =  timeElement ? [PIIntegration localDateForTime:timeNum.doubleValue]
			                               : [PIIntegration localDateForDate:dateNum.doubleValue];
			NSDate *today = [NSDate date];
			
			if (showOverdue && (dateNum.doubleValue != 0) && ([dueDate compare:today] == NSOrderedAscending)) 
				overdue = true;
			
			// If start and due are the same day then only display the one date, otherwise display the range
			if (isSameDay(startDate, dueDate)) {
				due = [PIIntegration formatDate:bundle
										   date:dateNum
										   time:timeElement
										 format:DateFormatterNormalFormat];
				shortDue = [PIIntegration formatDate:bundle
												date:dateNum
												time:timeElement
											  format:DateFormatterCompressedFormat];
			}
			else {
				due = [NSString stringWithFormat:@"%@ \u2014 %@",
					   [PIIntegration formatDate:bundle
											date:startNum
											time:false 
										  format:DateFormatterNormalFormat],
					   [PIIntegration formatDate:bundle
											date:dateNum
											time:timeElement
										  format:DateFormatterNormalFormat]];
				shortDue = [NSString stringWithFormat:@"%@ \u2014 %@",
							[PIIntegration formatDate:bundle
												 date:startNum
												 time:false 
											   format:DateFormatterCompressedFormat],
							[PIIntegration formatDate:bundle
												 date:dateNum
												 time:timeElement
											   format:DateFormatterCompressedFormat]];
			}
		}
	}
	else {
		// If we have a due date then add it to the single line
		if (dateNum.doubleValue != 0.0)
			name = [NSString stringWithFormat:@"%@ (%@)", name,
					[PIIntegration formatDate:bundle 
										 date:dateNum
										 time:(timeNum.doubleValue != 0)
									   format:DateFormatterNormalFormat]];
	}
	
	if (([priority length] == 0) && ([project length] > 0))
		priority = project;
	
	[v setViewForTable:tableView
			withPlugin:self
				 title:name
				   due:shortDates == ShortDatesAlways ? shortDue : due
			  dueShort:shortDates == ShortDatesNever ? due : shortDue
			   overdue:overdue
			importance:importance
			  priority:priority
				action:action
			   flagged:flagged
			 showIcons:showIcons];
	
	return cell;
}

- (CGFloat)tableView:(LITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
	BOOL showDate = [self getIntValueFromPrefs:self.plugin.preferences forName:@"showDates" withDefault:ShowDatesAlways];
	
	NSDictionary* elem = [self.todoList objectAtIndex:indexPath.row];
	
	NSNumber* startNum = [elem objectForKey:@"start"];
	NSNumber* dateNum = [elem objectForKey:@"due"];
	NSNumber* timeNum = [elem objectForKey:@"duetime"];
	NSString* priority = [elem objectForKey:@"priority"];

	tableView.theme.summaryStyle.font.leading;
	
	BOOL dateLine = (showDate == ShowDatesAlways) ||
	((showDate == ShowDatesIfNeeded) && ((startNum.doubleValue != 0) || (dateNum.doubleValue != 0) || (timeNum.doubleValue != 0) || ([priority length] > 0)));
	
	CGFloat result = [PITodoView nameRowHeightForFont:tableView.theme.summaryStyle.font];
	if (dateLine)
		result += [PITodoView detailRowHeightForFont:tableView.theme.detailStyle.font];
	
	return result;
}


#pragma mark LITableViewController methods

- (id) initWithPlugin:(LIPlugin*) pi {
	self = [super init];
	self.plugin = pi;
	
	pi.tableViewDataSource = self;
	pi.tableViewDelegate = self;
	
	NSLog(@"LI:PITodo: Initialised: %@", pi);
	DebugLog(@"LI:PITodo: bundleIdentifier: %@", pi.bundleIdentifier);
	DebugLog(@"LI:PITodo: bundle: %@", pi.bundle);
	DebugLog(@"LI:PITodo: globalBundle: %@", pi.globalBundle);
	DebugLog(@"LI:PITodo: preferences: %@", pi.preferences);
	DebugLog(@"LI:PITodo: globalPreferences: %@", pi.globalPreferences);
	DebugLog(@"LI:PITodo: managedBundles: %@", pi.managedBundles);
	
	NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
	
	[center addObserver:self selector:@selector(update:) name:LITimerNotification object:nil];
	[center addObserver:self selector:@selector(update:) name:LIViewReadyNotification object:nil];
	
	[center addObserver:self selector:@selector(update:) name:[@"pocketinformant" stringByAppendingString:LIPrefsUpdatedNotification] object:nil];
	
	//[center addObserver:self selector:@selector(update:) name:[plugin.bundleIdentifier stringByAppendingString:LIPrefsUpdatedNotification] object:nil];
	return self;
}

- (void) updateTasks {
	NSLog(@"LI:PITodo: Updating tasks...");
	
	// If we dont know the path to the DB then go find it
	if (self.dbPath == nil) {
		SBApplication* app = [PIIntegration getApplication];
		if (app != nil) {
			NSString* path = [app.path stringByDeletingLastPathComponent];
			
			self.appPath = [path stringByAppendingString:@"/Informant.app"];
			self.prefsPath = [path stringByAppendingFormat:@"/Library/Preferences/%@.plist", app.displayIdentifier];
			self.dbPath = [PIIntegration getDatabasePath:path];
			
			DebugLog(@"LI:PITodo: App Path: %@", self.appPath);
			DebugLog(@"LI:PITodo: Prefs Path: %@", self.prefsPath);
			DebugLog(@"LI:PITodo: DB Path: %@", self.dbPath);
		}
	}
	
	if (self.dbPath != nil) {
		self.todoPrefs = [NSDictionary dictionaryWithContentsOfFile:self.prefsPath];
		[self setApplicationColors:self.todoPrefs];
		
		NSString* sql = [self buildSQLString:self.plugin.preferences];
		
		NSLog(@"LI:PITodo: Executing SQL: %@", sql);
		
		/* Get the todo database timestamp */
		NSDictionary *dataFileAttributes = [[NSFileManager defaultManager] fileAttributesAtPath:self.dbPath traverseLink:YES];
		NSDate* lastDataModified = [dataFileAttributes objectForKey:NSFileModificationDate];
		
		if(![sql isEqualToString:self.lastSql] || lastUpdate < lastDataModified.timeIntervalSinceReferenceDate)
		{
			DebugLog(@"LI:PITodo: Loading Todo Tasks...");
			
			// Update data and read from database
			NSMutableArray *todos = [NSMutableArray arrayWithCapacity:4];
			self.lastSql = sql;
			
			sqlite3 *database = NULL;
			@try {		
				if (sqlite3_open([self.dbPath UTF8String], &database) != SQLITE_OK) {
					NSLog(@"LI:PITodo: Failed to open database.");
					return;
				}
				
				// Setup the SQL Statement and compile it for faster access
				sqlite3_stmt *compiledStatement = NULL;
				
				@try {
					if (sqlite3_prepare_v2(database, [sql UTF8String], -1, &compiledStatement, NULL) != SQLITE_OK) {
						NSLog(@"LI:PITodo: Failed to prepare statement: %s", sqlite3_errmsg(database));
						return;
					}
					
					DebugLog(@"LI:PITodo: Executed SQL OK");
					
					// Loop through the results and add them to the feeds array
					while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
						const char *cText = (const char*)sqlite3_column_text(compiledStatement, 0);
						double cStart = sqlite3_column_double(compiledStatement, 1);
						double cDue = sqlite3_column_double(compiledStatement, 2);
						double cDueTime = sqlite3_column_double(compiledStatement, 3);
						int importance = sqlite3_column_int(compiledStatement, 4);
						const char *cPriority = (const char*)sqlite3_column_text(compiledStatement, 5);
						int primaryKey = sqlite3_column_int(compiledStatement, 6);
						int flagged = sqlite3_column_int(compiledStatement, 7);
						int action = sqlite3_column_int(compiledStatement, 8);
						const char *cIcon = (const char*)sqlite3_column_text(compiledStatement, 9);
						const char *cProject = (const char*)sqlite3_column_text(compiledStatement, 10);
						
						NSString *aText = [NSString stringWithUTF8String:(cText == NULL ? "" : cText)];
						NSString *aIcon = [NSString stringWithUTF8String:(cIcon == NULL ? "" : cIcon)];
						NSString *aPriority = [NSString stringWithUTF8String:(cPriority == NULL ? "" : cPriority)];
						NSString *aProject = [NSString stringWithUTF8String:(cProject == NULL ? "" : cProject)];
						
						NSDictionary *todoDict = [NSDictionary dictionaryWithObjectsAndKeys:
												  aText, @"name",
												  [NSNumber numberWithDouble:cStart], @"start",
												  [NSNumber numberWithDouble:cDue], @"due",
												  [NSNumber numberWithDouble:cDueTime], @"duetime",
												  [NSNumber numberWithInt:importance], @"importance", 
												  aPriority, @"priority",
												  [NSNumber numberWithInt:flagged], @"flagged", 
												  aIcon, @"icon",
												  aProject, @"project",
												  [NSNumber numberWithInt:primaryKey], @"key", 
												  [NSNumber numberWithInt:action], @"action", 
												  nil];
						
						[todos addObject:todoDict];
					}
				}
				@catch (NSException *exception) {
					NSLog(@"LI:PITodo: DB Read exception: %@", exception);
				}
				@finally {			
					if (compiledStatement != NULL)
						sqlite3_finalize(compiledStatement);
				}
			}
			@finally {
				if (database != NULL)
					sqlite3_close(database);
			}
			
			[self performSelectorOnMainThread:@selector(setTodoList:) withObject:todos waitUntilDone:YES];	
			
			NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:1];
			[dict setObject:todos forKey:@"todos"];  
			
			DebugLog(@"LI:PITodo: Updating display records: %@", dict);
			[[NSNotificationCenter defaultCenter] postNotificationName:LIUpdateViewNotification object:self.plugin userInfo:dict];
			
			lastUpdate = lastDataModified.timeIntervalSinceReferenceDate;
		}
		else {
			//No change necessary
			NSLog(@"LI:PITodo: Skipping DB update - No SQL change of no DB file update");
		}
	}
	else {
		//There is no DB path
		NSLog(@"LI:PITodo: No DB Path");
	}
}

- (void) update:(NSNotification*) notif {
	if (!self.plugin.enabled)
		return;
	
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	[self updateTasks];
	[pool release];
}


#pragma mark LITableViewDelegate methods

-(UIView*) tableView:(LITableView*) tableView previewWithFrame:(CGRect) rect forRowAtIndexPath:(NSIndexPath*) indexPath {
	BOOL allowPreviews = [self getBoolValueFromPrefs:self.plugin.preferences forName:@"isShowPreviews" withDefault:true];
	
	if (indexPath.section >= self.todoList.count || !allowPreviews)
		return nil;
	
	PITodoPreview *thePreview = [[[PITodoPreview alloc] initWithFrame: &rect withList: self.todoList atIndex: indexPath withPlugin: self] autorelease];
	thePreview.backgroundColor = [UIColor whiteColor];
	
	return thePreview;
}

@end
